package cn.com.bluemoon.daps.common.datascope.sign;

import cn.com.bluemoon.daps.common.datascope.encry.AesWapper;
import cn.com.bluemoon.datasecurity.app.sdk.RemoteCryptoModuleService;
import cn.com.bluemoon.datasecurity.common.enums.BizExceptionEnum;
import cn.com.bluemoon.metadata.common.ResultBean;
import cn.com.bluemoon.metadata.common.exception.DescribeException;
import cn.com.bluemoon.metadata.common.utils.ApplicationContextHolder;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.util.StringUtil;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

@Slf4j
@Setter
@Component
public class ApiSignAuthFilter implements Filter {

    private SignProperties signProperties;

    public void setSignProperties(SignProperties signProperties) {
        this.signProperties = signProperties;
    }

    /**
     * 签名参数名称集合
     */
    private static final String BODY_HEADER = "x-body-header";
    private static String[] params = new String[]{"client", "cuid", "format", "time", "version", "sign"};
    private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            chain.doFilter(req, res);
            return;
        }
        String testPsw = request.getParameter("testSignPsw");
        //提供给测试的后门标识或非json类型或可跳过签名的接口，跳过验证
        RemoteCryptoModuleService cryptoKeyService = ApplicationContextHolder.context.getBean(RemoteCryptoModuleService.class);
        if (StrUtil.equals(testPsw, cryptoKeyService.getSignPsw().getContent())
                || !StrUtil.equalsIgnoreCase(request.getMethod(), HttpMethod.POST.name())
//                || !MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(request.getHeader(HttpHeaders.CONTENT_TYPE))
                || !dangerRequest(request)) {
            chain.doFilter(req, res);
        } else {
            //api参数签名校验
            try {
                chain.doFilter(verifySign(request), res);
            } catch (DescribeException e) {
                HttpServletResponse response = (HttpServletResponse) res;
                response.setContentType("application/json;charset=UTF-8");
                String originalURL = request.getHeader("Origin");
                if (originalURL != null) {
                    response.addHeader("Access-Control-Allow-Origin", originalURL);
                }
                response.addHeader("Access-Control-Allow-Credentials", "true");
                PrintWriter out = response.getWriter();
                ResultBean errorResultBean = new ResultBean<>();
                errorResultBean.setMsg(e.getMessage());
                errorResultBean.setSuccess(false);
                errorResultBean.setCode(e.getCode());
                out.append(JSONObject.toJSONString(errorResultBean));
            }
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {

    }

    /**
     * 请求判断是否为不安全地址（不安全地址不做签名）
     *
     * @param request
     * @return true-安全链接签名，false-不需要签名
     */
    private boolean dangerRequest(HttpServletRequest request) {
        String servletPath = request.getServletPath();
        while (servletPath.endsWith("/")) {
            /*去掉结尾的左斜线，方便ant_path匹配*/
            servletPath = servletPath.substring(0, servletPath.length() - 1);
        }
        String finalServletPath = servletPath;
        boolean matchExcludedUrl = false;
        if (!CollectionUtil.isEmpty(signProperties.getFilterUrls())) {
            matchExcludedUrl = signProperties.getFilterUrls().stream().anyMatch(
                    pattern -> ANT_PATH_MATCHER.match(pattern, finalServletPath)
            );
        }
        return matchExcludedUrl;
    }

    /***
     * 签名校验
     * @param request
     */
    private HttpServletRequestWrapper verifySign(HttpServletRequest request) {
        String client = getRequestParam(request, params[0]); // 客户端类型
        // 来源(如ios，Android、pc、wx)
        String cuid = getRequestParam(request, params[1]); // 设备号（mac地址）
        String format = getRequestParam(request, params[2]); // 请求格式 json
        String time = getRequestParam(request, params[3]); // 请求时间
        String version = getRequestParam(request, params[4]); // 应用版本号
        String sign = getRequestParam(request, params[5]); // 验证签名
        String bodyParam = getRequestParam(request, BODY_HEADER);

        if (StringUtil.isEmpty(client) || StringUtil.isEmpty(version) || StringUtil.isEmpty(cuid)
                || StringUtil.isEmpty(format) || StringUtil.isEmpty(time) || StringUtil.isEmpty(sign)) {
            throw new DescribeException( BizExceptionEnum.SIGN_ERROR_3.getResponseMsg(),BizExceptionEnum.SIGN_ERROR_3.getResponseCode());
        }
        if (DateUtil.between(new Date(Convert.toLong(time, 0l)), new Date(), DateUnit.MINUTE) > 15) {
            throw new DescribeException(BizExceptionEnum.SIGN_ERROR_1.getResponseMsg(),BizExceptionEnum.SIGN_ERROR_1.getResponseCode());
        }

        String bodyJsonStr = StrUtil.EMPTY;
        HttpServletRequestWrapper requestWrapper = null;
        // 如果参数加密了，就用加密后的参数，否则用body中的参数
        if (StringUtil.isNotEmpty(bodyParam)) {
            AesWapper aesWapper = ApplicationContextHolder.context.getBean(AesWapper.class);
            bodyJsonStr = aesWapper.decryptStr(bodyParam);
            if (StrUtil.isBlank(bodyJsonStr)) {
                bodyJsonStr = StrUtil.EMPTY;
            }
            // 写入到body里面
            try {
                requestWrapper = new CommonReaderHttpServletRequestWrapper(request,bodyJsonStr);
            } catch (IOException e) {
                log.error("解析请求头中的body异常", e);
                throw new DescribeException(BizExceptionEnum.SIGN_ERROR_2.getResponseMsg(),BizExceptionEnum.SIGN_ERROR_2.getResponseCode());
            }

        }else{

            if (!request.getRequestURI().contains("upload")) {
                try {
                    BodyReaderHttpServletRequestWrapper bodyReaderHttpServletRequestWrapper = new BodyReaderHttpServletRequestWrapper(request);
                    bodyJsonStr = bodyReaderHttpServletRequestWrapper.getRequestPostStr(bodyReaderHttpServletRequestWrapper);
                    requestWrapper = bodyReaderHttpServletRequestWrapper;
                    if (StrUtil.isBlank(bodyJsonStr)) {
                        bodyJsonStr = StrUtil.EMPTY;
                    }
                } catch (IOException e) {
                    log.error("解析请求body异常", e);
                    throw new DescribeException(BizExceptionEnum.SIGN_ERROR_2.getResponseMsg(),BizExceptionEnum.SIGN_ERROR_2.getResponseCode());
                }
            }
        }

        //拼接签名
        String signPsw = ApplicationContextHolder.context.getBean(RemoteCryptoModuleService.class).getSignPsw().getContent();
        StringBuilder signSource = new StringBuilder();
        signSource.append(signPsw).append(client).append(cuid).append(format).append(time).append(version)
                .append(bodyJsonStr).append(signPsw);

        String keySign = SecureUtil.md5(signSource.toString()).toLowerCase();
        if (!StrUtil.equals(keySign, sign)) {
            log.error("接口{}签名验证失败,请求签名串：{}，sign：{}", request.getRequestURL(), signSource, sign);
            throw new DescribeException(BizExceptionEnum.SIGN_ERROR.getResponseMsg(),BizExceptionEnum.SIGN_ERROR.getResponseCode());
        }
        return requestWrapper;
    }

    /**
     * 从request中获取参数，先重head获取没有再从请求url上的参数中获取
     *
     * @param request
     * @param key
     * @return
     */
    private String getRequestParam(HttpServletRequest request, String key) {
        String value = request.getHeader(key);
        if (StrUtil.isBlank(value)) {
            value = request.getParameter(key);
        }
        return value;
    }
}